/*
 * Copyright (C) 2014 Miquel Sas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package traderlib.core.data.types;

import java.math.BigDecimal;
import java.util.List;

/**
 * A generic container for a value of different types. Valid types for a Value are Boolean, String, Decimal, Double,
 * Integer, Long, Date, Time, Timestamp, ByteArray.
 *
 * Note that other types will be included uppon need.
 *
 * @author Miquel Sas
 */
public class Value implements Comparable<Object> {

	/**
	 * Create a copy of a list of values.
	 *
	 * @param values The list of values to copy.
	 * @return The copy of the list.
	 */
	public static Value[] copy(Value[] values) {
		Value[] newValues = new Value[values.length];
		for (int i = 0; i < newValues.length; i++) {
			newValues[i] = new Value(values[i]);
		}
		return newValues;
	}

	/**
	 * Check if a list of values are full.
	 *
	 * @param values The list of values
	 * @return A boolean
	 */
	public static boolean areFull(Value... values) {
		boolean full = true;
		for (Value value : values) {
			if (value.isEmpty()) {
				full = false;
				break;
			}
		}
		return full;
	}

	/**
	 * Check if a list of values are empty.
	 *
	 * @param values The list of values
	 * @return A boolean
	 */
	public static boolean areEmpty(Value... values) {
		boolean empty = true;
		for (Value value : values) {
			if (!value.isEmpty()) {
				empty = false;
				break;
			}
		}
		return empty;
	}

	/**
	 * Returns true if this value is in the list, false otherwise.
	 *
	 * @param value The value to check.
	 * @param values The list of values to check.
	 * @return True if this value is in the list.
	 */
	public static boolean in(Value value, Value... values) {
		return indexOf(value, values) >= 0;
	}

	/**
	 * Returns true if this value is in the list, false otherwise.
	 *
	 * @param value The value to check.
	 * @param values The list of values to check.
	 * @return True if this value is in the list.
	 */
	public static boolean in(Value value, List<Value> values) {
		return indexOf(value, values) >= 0;
	}

	/**
	 * Returns the index of this value in a list of values, or -1 if this value is not in the list.
	 *
	 * @param value The value to check.
	 * @param values The list of values.
	 * @return The index of this value in the list or -1.
	 */
	public static int indexOf(Value value, Value... values) {
		for (int i = 0; i < values.length; i++) {
			if (value.equals(values[i])) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Returns the index of this value in a list of values, or -1 if this value is not in the list.
	 *
	 * @param value The value to check.
	 * @param values The list of values.
	 * @return The index of this value in the list or -1.
	 */
	public static int indexOf(Value value, List<Value> values) {
		return indexOf(value, values.toArray(new Value[values.size()]));
	}

	/**
	 * The value type.
	 */
	private Types type = null;
	/**
	 * The value itself.
	 */
	private Object value = null;
	/**
	 * Modified flag.
	 */
	private boolean modified = false;
	/**
	 * An optional label for the value.
	 */
	private String label = null;

	/**
	 * Copy constructor.
	 *
	 * @param v A value
	 */
	public Value(Value v) {
		super();
		if (v.label != null) {
			label = v.getLabel();
		}
		type = v.getType();
		if (v.isNull()) {
			return;
		}
		value = v.getValue();
	}

	/**
	 * Constructor assigning a boolean.
	 *
	 * @param b A boolean
	 */
	public Value(boolean b) {
		super();
		value = b;
		type = Types.Boolean;
	}

	/**
	 * Constructor assigning a boolean.
	 *
	 * @param b A boolean
	 */
	public Value(Boolean b) {
		super();
		value = b;
		type = Types.Boolean;
	}

	/**
	 * Constructor assigning a string.
	 *
	 * @param s A string
	 */
	public Value(String s) {
		super();
		value = s;
		type = Types.String;
	}

	/**
	 * Constructor assigning a number with precision.
	 *
	 * @param b A big decimal
	 */
	public Value(BigDecimal b) {
		super();
		value = b;
		type = Types.Decimal;
	}

	/**
	 * Constructor assigning a double.
	 *
	 * @param d A double
	 */
	public Value(Double d) {
		super();
		value = d;
		type = Types.Double;
	}

	/**
	 * Constructor assigning a double.
	 *
	 * @param d A double
	 */
	public Value(double d) {
		super();
		value = d;
		type = Types.Double;
	}

	/**
	 * Constructor assigning an integer.
	 *
	 * @param i An integer
	 */
	public Value(Integer i) {
		super();
		value = i;
		type = Types.Integer;
	}

	/**
	 * Constructor assigning an integer.
	 *
	 * @param i An integer
	 */
	public Value(int i) {
		super();
		value = i;
		type = Types.Integer;
	}

	/**
	 * Constructor assigning a long.
	 *
	 * @param l A long
	 */
	public Value(Long l) {
		super();
		value = l;
		type = Types.Long;
	}

	/**
	 * Constructor assigning a long.
	 *
	 * @param l A long
	 */
	public Value(long l) {
		super();
		value = l;
		type = Types.Long;
	}

	/**
	 * Constructor assigning a ByteArray
	 *
	 * @param byteArray The ByteArray
	 */
	public Value(ByteArray byteArray) {
		super();
		value = byteArray;
		type = Types.ByteArray;
	}

	/**
	 * Constructor assigning a binary (byte[])
	 *
	 * @param b The byte array
	 */
	public Value(byte[] b) {
		super();
		ByteArray byteArray = new ByteArray(b.length);
		byteArray.addAll(b);
		value = byteArray;
		type = Types.ByteArray;
	}

	/**
	 * Constructor assigning a date.
	 *
	 * @param d The date
	 */
	public Value(Date d) {
		super();
		value = d;
		type = Types.Date;
	}

	/**
	 * Constructor assigning a time.
	 *
	 * @param t The time
	 */
	public Value(Time t) {
		super();
		value = t;
		type = Types.Time;
	}

	/**
	 * Constructor assigning a timestamp.
	 *
	 * @param t The timestamp
	 */
	public Value(Timestamp t) {
		super();
		value = t;
		type = Types.Timestamp;
	}

	/**
	 * Returns a negative integer, zero, or a positive integer as this value is less than, equal to, or greater than the
	 * specified value.
	 *
	 * @param o The object to compare.
	 * @return The compartion integer.
	 */
	@Override
	public int compareTo(Object o) {
		// Argument cant be null
		if (o == null) {
			throw new NullPointerException();
		}
		// Must be the same type
		Value v = null;
		try {
			v = (Value) o;
		} catch (ClassCastException exc) {
			throw new UnsupportedOperationException("Not comparable types");
		}

		// Null types
		if (isNull() && v.isNull()) {
			return 0;
		}
		if (isNull() && !v.isNull()) {
			return -1;
		}
		if (!isNull() && v.isNull()) {
			return 1;
		}
		// Compare only if comparable
		if (isBoolean()) {
			if (!v.isBoolean()) {
				throw new IllegalArgumentException("Not comparable types");
			}
			boolean b1 = getBoolean();
			boolean b2 = v.getBoolean();
			return (!b1 && b2 ? -1 : (b1 && !b2 ? 1 : 0));
		}
		if (isNumber()) {
			if (!v.isNumber()) {
				throw new IllegalArgumentException("Not comparable types");
			}
			double d1 = getDouble();
			double d2 = v.getDouble();
			return Double.compare(d1, d2);
		}
		if (isString()) {
			if (!v.isString()) {
				throw new IllegalArgumentException("Not comparable types");
			}
			return getString().compareTo(v.getString());
		}
		if (isDate()) {
			if (!v.isDate()) {
				throw new IllegalArgumentException("Not comparable types");
			}
			return getDate().compareTo(v.getDate());
		}
		if (isTime()) {
			if (!v.isTime()) {
				throw new IllegalArgumentException("Not comparable types");
			}
			return getTime().compareTo(v.getTime());
		}
		if (isTimestamp()) {
			if (!v.isTimestamp()) {
				throw new IllegalArgumentException("Not comparable types");
			}
			return getTimestamp().compareTo(v.getTimestamp());
		}
		if (isByteArray()) {
			if (!v.isByteArray()) {
				throw new IllegalArgumentException("Not comparable types");
			}
			return getByteArray().compareTo(v.getByteArray());
		}
		throw new IllegalArgumentException("Value " + toString() + " is not comparable");
	}

	/**
	 * Indicates whether some other object is "equal to" this one.
	 *
	 * @return A boolean.
	 * @param o The object to compare with.
	 */
	@Override
	public boolean equals(Object o) {
		// Null
		if (o == null) {
			return isNull();
		} else {
			if (isNull()) {
				return false;
			}
		}
		// Boolean
		if (o instanceof Boolean) {
			if (!isBoolean()) {
				return false;
			}
			Boolean b = (Boolean) o;
			return getBoolean().equals(b);
		}
		// String
		if (o instanceof String) {
			if (!isString()) {
				return false;
			}
			String s = (String) o;
			return getString().equals(s);
		}
		// Decimal, Double, Integer, Long
		if (o instanceof Number) {
			if (!isNumber()) {
				return false;
			}
			Number n = (Number) o;
			return getNumber().equals(n);
		}
		// Date
		if (o instanceof Date) {
			if (!isDate()) {
				return false;
			}
			Date d = (Date) o;
			return getDate().equals(d);
		}
		// Time
		if (o instanceof Time) {
			if (!isTime()) {
				return false;
			}
			Time t = (Time) o;
			return getTime().equals(t);
		}
		// Timestamp
		if (o instanceof Timestamp) {
			if (!isTimestamp()) {
				return false;
			}
			Timestamp t = (Timestamp) o;
			return getTimestamp().equals(t);
		}
		// ByteArray
		if (o instanceof ByteArray) {
			if (!isByteArray()) {
				return false;
			}
			ByteArray byteArray = (ByteArray) o;
			return getByteArray().equals(byteArray);
		}
		// Value
		if (o instanceof Value) {
			Value v = (Value) o;
			// Types must be the same except for numbers where the number must be the same
			if ((isBoolean() && !v.isBoolean()) || (!isBoolean() && v.isBoolean())) {
				return false;
			}
			if ((isString() && !v.isString()) || (!isString() && v.isString())) {
				return false;
			}
			if ((isDateTimeOrTimestamp() && !v.isDateTimeOrTimestamp()) || (!isDateTimeOrTimestamp() && v.isDateTimeOrTimestamp())) {
				return false;
			}
			if ((isNumber() && !v.isNumber()) || (!isNumber() && v.isNumber())) {
				return false;
			}
			if ((isByteArray() && !v.isByteArray()) || (!isByteArray() && v.isByteArray())) {
				return false;
			}
			if (isBoolean()) {
				return getBoolean().equals(v.getBoolean());
			}
			if (isString()) {
				return getString().equals(v.getString());
			}
			if (isDateTimeOrTimestamp()) {
				return getTimestamp().equals(v.getTimestamp());
			}
			if (isNumber()) {
				return getNumber().equals(v.getNumber());
			}
			if (isByteArray()) {
				return getByteArray().equals(v.getByteArray());
			}
		}
		return false;
	}

	/**
	 * Direct equals.
	 *
	 * @param b Boolean to compare with.
	 * @return A boolean
	 */
	public boolean equals(boolean b) {
		return equals(Boolean.valueOf(b));
	}

	/**
	 * Direct equals.
	 *
	 * @param bytes Array of bytes to compare with.
	 * @return A boolean
	 */
	public boolean equals(byte[] bytes) {
		ByteArray byteArray = new ByteArray(bytes.length);
		byteArray.addAll(bytes);
		return equals(byteArray);
	}

	/**
	 * Direct equals.
	 *
	 * @param d Value to compare with.
	 * @return A boolean
	 */
	public boolean equals(double d) {
		return equals(Double.valueOf(d));
	}

	/**
	 * Direct equals.
	 *
	 * @param i The integer to compare with.
	 * @return A boolean
	 */
	public boolean equals(int i) {
		return equals(Integer.valueOf(i));
	}

	/**
	 * Direct equals.
	 *
	 * @param l The long to compare with.
	 * @return A boolean
	 */
	public boolean equals(long l) {
		return equals(Long.valueOf(l));
	}

	/**
	 * Returns the hash code for this value.
	 *
	 * @return The hash code
	 */
	@Override
	public int hashCode() {
		if (!isNull()) {
			if (isBoolean()) {
				Boolean b = getBoolean();
				return b.hashCode();
			}
			if (isByteArray()) {
				return getByteArray().hashCode();
			}
			if (isNumber()) {
				Double d = getDouble();
				return d.hashCode();
			}
			if (isString()) {
				return getString().hashCode();
			}
			if (isDate()) {
				return getDate().hashCode();
			}
			if (isTime()) {
				return getTime().hashCode();
			}
			if (isTimestamp()) {
				return getTimestamp().hashCode();
			}
		}
		return Integer.MIN_VALUE;
	}

	/**
	 * Returns this value type.
	 *
	 * @return The type.
	 */
	public Types getType() {
		return type;
	}

	/**
	 * Get the value as a <code>boolean</code>.
	 *
	 * @return A boolean
	 */
	public Boolean getBoolean() {
		if (isBoolean()) {
			if (isNull()) {
				return false;
			}
			return ((Boolean) value);
		}
		throw new UnsupportedOperationException("Value " + value + " is not a boolean.");
	}

	/**
	 * Get the value as a <code>String</code>.
	 *
	 * @return A String
	 */
	public String getString() {
		if (isString()) {
			return (String) value;
		}
		throw new UnsupportedOperationException("Value " + value + " is not a string.");
	}

	/**
	 * Get the value as a <code>Date</code>.
	 *
	 * @return A Date
	 */
	public Date getDate() {
		if (isTimestamp()) {
			return new Date(getTimestamp().getTime());
		}
		if (isDate()) {
			return (Date) value;
		}
		if (isTime()) {
			return new Date(getTime().getTime());
		}
		throw new UnsupportedOperationException("Value " + value + " is not a date.");
	}

	/**
	 * Get the value as a <code>Time</code>.
	 *
	 * @return A Time
	 */
	public Time getTime() {
		if (isTimestamp()) {
			return new Time(getTimestamp().getTime());
		}
		if (isDate()) {
			return new Time(getDate().getTime());
		}
		if (isTime()) {
			return (Time) value;
		}
		throw new UnsupportedOperationException("Value " + value + " is not a time.");
	}

	/**
	 * Get the value as a <code>Timestamp</code>.
	 *
	 * @return A Timestamp
	 */
	public Timestamp getTimestamp() {
		if (isTimestamp()) {
			return (Timestamp) value;
		}
		if (isDate()) {
			return new Timestamp(getDate().getTime());
		}
		if (isTime()) {
			return new Timestamp(getTime().getTime());
		}
		throw new UnsupportedOperationException("Value " + value + " is not a timestamp, date or time.");
	}

	/**
	 * Get the value as a ByteArray.
	 *
	 * @return A byte[]
	 */
	public ByteArray getByteArray() {
		if (isByteArray()) {
			return (ByteArray) value;
		}
		throw new UnsupportedOperationException("Value " + value + " is not Bytearray.");
	}

	/**
	 * Get the value as a number if it is so.
	 *
	 * @return The number.
	 */
	public Number getNumber() {
		if (isNumber()) {
			return (Number) value;
		}
		throw new UnsupportedOperationException("Value " + value + " is not a number.");
	}

	/**
	 * Check if this value is boolean.
	 *
	 * @return A boolean.
	 */
	public boolean isBoolean() {
		return getType().isBoolean();
	}

	/**
	 * Check if this value is a string.
	 *
	 * @return A boolean.
	 */
	public boolean isString() {
		return getType().isString();
	}

	/**
	 * Check if this value is a number (decimal) with fixed precision.
	 *
	 * @return A boolean.
	 */
	public boolean isDecimal() {
		return getType().isDecimal();
	}

	/**
	 * Check if this value is a double.
	 *
	 * @return A boolean.
	 */
	public boolean isDouble() {
		return getType().isDouble();
	}

	/**
	 * Check if this value is an integer.
	 *
	 * @return A boolean.
	 */
	public boolean isInteger() {
		return getType().isInteger();
	}

	/**
	 * Check if this value is a long.
	 *
	 * @return A boolean.
	 */
	public boolean isLong() {
		return getType().isLong();
	}

	/**
	 * Check if this value is a number (decimal, double or integer).
	 *
	 * @return A boolean.
	 */
	public boolean isNumber() {
		return getType().isNumber();
	}

	/**
	 * Check if this value is a floating point number.
	 *
	 * @return A boolean.
	 */
	public boolean isFloatingPoint() {
		return getType().isFloatingPoint();
	}

	/**
	 * Check if this value is a date.
	 *
	 * @return A boolean.
	 */
	public boolean isDate() {
		return getType().isDate();
	}

	/**
	 * Check if this value is a time.
	 *
	 * @return A boolean.
	 */
	public boolean isTime() {
		return getType().isTime();
	}

	/**
	 * Check if this value is a time.
	 *
	 * @return A boolean.
	 */
	public boolean isTimestamp() {
		return getType().isTimestamp();
	}

	/**
	 * Check if this value is a date, time or timestamp.
	 *
	 * @return A boolean.
	 */
	public boolean isDateTimeOrTimestamp() {
		return getType().isDateTimeOrTimestamp();
	}

	/**
	 * Check if this value is binary (byte[]).
	 *
	 * @return A boolean.
	 */
	public boolean isByteArray() {
		return getType().isTimestamp();
	}

	/**
	 * Check if this value is null. Null is not a type, but a value can be null if the holder object its so.
	 *
	 * @return A boolean indicating if the value is null.
	 */
	public boolean isNull() {
		return (value == null);
	}

	/**
	 * Check if the value is empty, that is, null, empty string or zero if is number.
	 *
	 * @return A boolean
	 */
	public boolean isEmpty() {
		if (isNull()) {
			return true;
		}
		if (isString() && getString().length() == 0) {
			return true;
		}
		return isNumber() && getDouble() == 0;
	}

	/**
	 * Check if this value is empty or a blank string (only spaces).
	 *
	 * @return A boolean
	 */
	public boolean isBlank() {
		return isEmpty() || (isString() && getString().trim().length() == 0);
	}

	/**
	 * Get the value as a BigDecimal it it's a number, otherwise throw an exception.
	 *
	 * @return A BigDecimal
	 */
	public BigDecimal getBigDecimal() {
		if (isDecimal()) {
			return (BigDecimal) value;
		}
		if (isNumber()) {
			return new BigDecimal(getDouble());
		}
		throw new UnsupportedOperationException("Value " + value + " is not a number.");
	}

	/**
	 * Get the value as a double it it's a number, otherwise throw an exception.
	 *
	 * @return A double
	 */
	public Double getDouble() {
		if (isNumber()) {
			return ((Number) value).doubleValue();
		}
		throw new UnsupportedOperationException("Value " + value + " is not a number.");
	}

	/**
	 * Get the value as an <code>int</code>.
	 *
	 * @return An int
	 */
	public Integer getInteger() {
		if (isNumber()) {
			return ((Number) value).intValue();
		}
		throw new UnsupportedOperationException("Value " + value + " is not a number.");
	}

	/**
	 * Get the optional label.
	 *
	 * @return The label.
	 */
	public String getLabel() {
		return label;
	}

	/**
	 * Get the value as an <code>long</code>.
	 *
	 * @return A long
	 */
	public Long getLong() {
		if (isNumber()) {
			return ((Number) value).longValue();
		}
		throw new UnsupportedOperationException("Value " + value + " is not a number.");
	}

	/**
	 * Return this value scale.
	 *
	 * @return The scale or number of decimal places.
	 */
	public int getScale() {
		if (isNull() || !isNumber()) {
			return 0;
		}
		if (isDecimal()) {
			return getBigDecimal().scale();
		}
		if (isDouble()) {
			String sdouble = Double.toString(getDouble());
			int dot = sdouble.indexOf('.');
			if (dot >= 0) {
				return sdouble.substring(dot + 1).length();
			}
		}
		return 0;
	}

	/**
	 * Set this value to NULL.
	 */
	public void setNull() {
		value = null;
		modified = true;
	}

	/**
	 * Set the value.
	 *
	 * @param b A BigDecimal
	 */
	public void setBigDecimal(BigDecimal b) {
		if (!isNumber()) {
			throw new UnsupportedOperationException("Value " + value + " is not a number.");
		}
		modified = true;
		if (b == null) {
			setNull();
			return;
		}
		if (isDecimal()) {
			value = b.setScale(getBigDecimal().scale(), BigDecimal.ROUND_HALF_UP);
			return;
		}
		if (isDouble()) {
			value = b.doubleValue();
			return;
		}
		if (isInteger()) {
			value = b.intValue();
			return;
		}
		if (isLong()) {
			value = b.longValue();
			return;
		}
		throw new UnsupportedOperationException("Unexpected exception");
	}

	/**
	 * Set the value.
	 *
	 * @param b A boolean
	 */
	public void setBoolean(boolean b) {
		if (!isBoolean()) {
			throw new UnsupportedOperationException("Value " + value + " is not a boolean.");
		}
		modified = true;
		value = b;
		return;
	}

	/**
	 * Set the value.
	 *
	 * @param bytes A byte[]
	 */
	public void setByteArray(byte[] bytes) {
		ByteArray byteArray = new ByteArray(bytes.length);
		byteArray.addAll(bytes);
		setByteArray(byteArray);
	}

	/**
	 * Set the value.
	 *
	 * @param byteArray A ByteArray
	 */
	public void setByteArray(ByteArray byteArray) {
		if (!isNull() && !isByteArray()) {
			throw new UnsupportedOperationException("Value " + value + " is not binary.");
		}
		if (byteArray == null) {
			setNull();
			return;
		}
		if (isNull()) {
			modified = true;
			value = byteArray;
			type = Types.ByteArray;
			return;
		}
		if (isByteArray()) {
			modified = true;
			value = byteArray;
			return;
		}
	}

	/**
	 * Set the value.
	 *
	 * @param d A Date
	 */
	public void setDate(Date d) {
		if (!isDate()) {
			throw new UnsupportedOperationException("Value " + value + " is not a date.");
		}
		modified = true;
		if (d == null) {
			setNull();
			return;
		}
		value = d;
		return;
	}
	
	public void setString(String s) {
		if (!isString()) {
			throw new UnsupportedOperationException("Value " + value + " is not a string.");
		}
		modified = true;
		if (s == null) {
			setNull();
			return;
		}
		value = s;
		return;
	}

	/**
	 * Set the value.
	 *
	 * @param t A Time
	 */
	public void setTime(Time t) {
		if (!isTime()) {
			throw new UnsupportedOperationException("Value " + value + " is not a time.");
		}
		modified = true;
		if (t == null) {
			setNull();
			return;
		}
		value = t;
		return;
	}

	/**
	 * Set the value.
	 *
	 * @param t A Timestamp
	 */
	public void setTimestamp(Timestamp t) {
		if (!isTimestamp()) {
			throw new UnsupportedOperationException("Value " + value + " is not a time.");
		}
		modified = true;
		if (t == null) {
			setNull();
			return;
		}
		value = t;
		return;
	}

	/**
	 * Set the value.
	 *
	 * @param d The double value
	 */
	public void setDouble(double d) {
		if (!isNumber()) {
			throw new UnsupportedOperationException("Value " + value + " is not a number.");
		}
		modified = true;
		if (isDouble()) {
			value = d;
			return;
		}
		if (isDecimal()) {
			BigDecimal b = new BigDecimal(d).setScale(getBigDecimal().scale(), BigDecimal.ROUND_HALF_UP);
			value = b;
			return;
		}
		if (isInteger()) {
			value = (int) d;
			return;
		}
		if (isLong()) {
			value = (long) d;
			return;
		}
		throw new UnsupportedOperationException("Unexpected exception");
	}

	/**
	 * Set the value.
	 *
	 * @param i An int
	 */
	public void setInt(int i) {
		if (!isNumber()) {
			throw new UnsupportedOperationException("Value " + value + " is not a number.");
		}
		modified = true;
		if (isInteger()) {
			value = i;
			return;
		}
		if (isLong()) {
			value = (long) i;
			return;
		}
		if (isDouble()) {
			value = (double) i;
			return;
		}
		if (isDecimal()) {
			BigDecimal b = new BigDecimal(i).setScale(getBigDecimal().scale(), BigDecimal.ROUND_HALF_UP);
			value = b;
			return;
		}
		throw new UnsupportedOperationException("Unexpected exception");
	}

	/**
	 * Set the optional label.
	 *
	 * @param label The label.
	 */
	public void setLabel(String label) {
		this.label = label;
	}

	/**
	 * Set the value.
	 *
	 * @param l A long
	 */
	public void setLong(long l) {
		if (!isNumber()) {
			throw new UnsupportedOperationException("Value " + value + " is not a number.");
		}
		modified = true;
		if (isInteger()) {
			value = (int) l;
			return;
		}
		if (isLong()) {
			value = l;
			return;
		}
		if (isDouble()) {
			value = (double) l;
			return;
		}
		if (isDecimal()) {
			BigDecimal b = new BigDecimal(l).setScale(getBigDecimal().scale(), BigDecimal.ROUND_HALF_UP);
			value = b;
			return;
		}
		throw new UnsupportedOperationException("Unexpected exception");
	}

	/**
	 * Check if this value has been modified calling a setter method.
	 *
	 * @return the modified flag
	 */
	public boolean isModified() {
		return modified;
	}

	/**
	 * Privately access the value
	 *
	 * @return The value
	 */
	private Object getValue() {
		return value;
	}

	/**
	 * Returns a string representation of this value.
	 *
	 * @return A string
	 */
	@Override
	public String toString() {
		if (isNull()) {
			return "null";
		}
		if (isByteArray()) {
			return new String((byte[]) value);
		}
		return value.toString();
	}

	/**
	 * Set the modified flag.
	 *
	 * @param modified The flag that indicates that the values shoud be considered modified.
	 */
	public void setModified(boolean modified) {
		this.modified = modified;
	}

	/**
	 * Returns true if this value is in the list, false otherwise.
	 *
	 * @param values The list of values to check.
	 * @return True if this value is in the list.
	 */
	public boolean in(Value... values) {
		return in(this, values);
	}

	/**
	 * Returns true if this value is in the list, false otherwise.
	 *
	 * @param values The list of values to check.
	 * @return True if this value is in the list.
	 */
	public boolean in(List<Value> values) {
		return in(this, values);
	}

	/**
	 * Returns true if this value is not in the list, false otherwise.
	 *
	 * @param values The list of values to check.
	 * @return True if this value is not in the list.
	 */
	public boolean notIn(Value... values) {
		return !in(this, values);
	}

	/**
	 * Returns true if this value is not in the list, false otherwise.
	 *
	 * @param values The list of values to check.
	 * @return True if this value is not in the list.
	 */
	public boolean notIn(List<Value> values) {
		return !in(this, values);
	}
}
